package com.nokia.pc3.core.xform;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

import com.nokia.pc3.core.dac.DAC;
import com.nokia.pc3.core.dac.NokiaUmem;

/**
 * Transformer factory. Uses workflow xformType in configuration descriptor.
 * 
 * @author jcroke
 * 
 */
public class TransformFactory {

	static final String CLASS_NOP = "com.nokia.pc3.core.transformers.NOPXfm";
	public static final String DEFAULT_NOP_INPUT = "__input";
	// Common default input label.
	public static final String DEFAULT_NOP_OUTPUT = "input";
	static final String DEFAULT_NOP_NAME = "Default_NOP";

	private final DAC dac;

	public TransformFactory(DAC dac) {
		this.dac = dac;
	}

	public Transform getTransform(String xformType)
			throws ClassNotFoundException {
		ArrayList<Hashtable<String, Object>> transformerList = new ArrayList<Hashtable<String, Object>>();

		// create a new NokiaUmem object per transformation series.
		NokiaUmem umem = new NokiaUmem();
		Transform ret = null;
		ArrayList<Xformation> XformationList = dac.getXformationType(xformType);

		Xformation lastXformation = null;

		for (Xformation xfn : XformationList) {
			// Last transformation of type x will be saved for its
			// other properties (e.g xformOutlist) - which means
			// you should not have multiple transformations under the
			// same type name. [FOR NOW]
			lastXformation = xfn;

			ArrayList<Xformer> xfs = xfn.getXformList();
			Iterator<Xformer> xfsIterator = xfs.iterator();
			ret = new Transform(umem, lastXformation);

			while (xfsIterator.hasNext()) {
				Xformer xf = xfsIterator.next();
				Hashtable<String, Object> xfRealized = new Hashtable<String, Object>();

				// should pass args here if any. not sure how, but
				// it would be better for the object if it had
				// expensive setup.
				Transformer xo = createXformer(xf, Transformer.class);

				xo.init(ret, xf);

				filterNull(xfRealized, Xformer.FNAME, xf.getFname());
				filterNull(xfRealized, Xformer.FOBJECT, xo);
				filterNull(xfRealized, Xformer.FINPUT, xf.getInput());
				filterNull(xfRealized, Xformer.FOUTPUT, xf.getOutput());
				filterNull(xfRealized, Xformer.FARGS_DELIM, xf.getArgsDelim());
				filterNull(xfRealized, Xformer.FARGS, xf.getArgs());
				filterNull(xfRealized, Xformer.FLABELS, xf.getLabels());

				transformerList.add(xfRealized);
			}
		}

		// No defined xforms implies a No-Op transformation.
		if (transformerList.isEmpty()) {
			ret = new Transform(umem, null);
			Xformer NoXf = new Xformer();
			NoXf.setFclass(TransformFactory.CLASS_NOP);
			// Default is a single output.
			// Assume that input seeding will be handled by caller.
			NoXf.setInput(TransformFactory.DEFAULT_NOP_INPUT);
			NoXf.setOutput(TransformFactory.DEFAULT_NOP_OUTPUT);
			NoXf.setFname(TransformFactory.DEFAULT_NOP_NAME);

			Hashtable<String, Object> NoXfRealized = new Hashtable<String, Object>();
			Transformer xo = createXformer(NoXf, Transformer.class);
			xo.init(ret, NoXf);

			filterNull(NoXfRealized, Xformer.FNAME, NoXf.getFname());
			filterNull(NoXfRealized, Xformer.FOBJECT, xo);
			filterNull(NoXfRealized, Xformer.FINPUT, NoXf.getInput());
			filterNull(NoXfRealized, Xformer.FOUTPUT, NoXf.getOutput());

			transformerList.add(NoXfRealized);
		}
		ret.setTransformation(transformerList);
		return ret;
	}

	static private void filterNull(Hashtable<String, Object> filtered,
			String key, Object value) {
		if (value != null) {
			filtered.put(key, value);
		}
	}

	protected <T> T createXformer(Xformer xf, Class<T> ifaceClass)
			throws ClassNotFoundException {
		Class<? extends T> clazz = Class.forName(xf.getFclass()).asSubclass(
				ifaceClass);
		return createXFInstance(clazz);
	}

	protected <T> T createXFInstance(final Class<T> classToCreate) {
		final Constructor<T> constructor;
		T result = null;
		try {
			constructor = classToCreate.getDeclaredConstructor();
			result = constructor.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

}
